/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <vfs.h>
/* includes (standard library, system) ---------------------------------------*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
/* includes (other library) --------------------------------------------------*/
#include <x_config.h>

#ifdef CONFIG_CMDER
#include <stdio.h>
#include <cmder.h>
#include <k_task.h>
#endif
/* includes (project) --------------------------------------------------------*/
#include <x_compiler.h>
#include <x_list.h>
#include <x_module.h>
#include <x_warn.h>
#include <x_bug.h>
#include <x_errno.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static dlist_t fs_head = DLIST_INIT(fs_head);
static dlist_t mp_head = DLIST_INIT(mp_head);
static k_mutex_t fs_lock;

static struct fildes fd_table[CONFIG_MAX_NR_FILES] = {0};

/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/*
 * @brief get mount_point struct by using path to match mp_path
 *
 * note:
 * 1) this routine must be invoked with the fs_lock locked
 * 2) there is no '//' in <path>, see __get_new_path()
 *
 * @param path      [IN]    the formatted path, like: "/fs", "/fs/file"
 * @param rela_path [OUT]   the path relative to mp_path
 *
 * @return the mount_point struct pointer or NULL
 *
 * remarks:
 * 1) if retval == NULL,      then the path is invalid
 * 2) if **rela_path == '\0', then the path is equal to mp_path
 */
static struct mount_point *__path_resolve(const char *path, const char **rela_path)
{
    dlist_t *iter;
    struct mount_point *best = NULL;
    size_t best_len = 0;

    dlist_foreach(iter, &mp_head)
    {
        size_t len; /* mount point path string length */
        struct mount_point *mp;
        const char *sub;

        mp = container_of(iter, struct mount_point, mp_node);

        sub = strstr(path, mp->mp_path);

        if ((sub == NULL) || sub != path) {
            continue;
        }

        len = strlen(mp->mp_path);

        if (path[len] == '\0') {
            best_len = len - 1; /* so, best_len + 1 == len */
            best = mp;
            break;
        }

        if ((len > best_len) && (path[len] == '/')) {
            best_len = len;
            best = mp;
        }
    }

    if (rela_path != NULL) {
        *rela_path = path + best_len + 1; /* plus 1 to skip the '/' */
    }

    return best;
}

static int __get_fildes(const char *path)
{
    int i;

    for (i = 0; i < CONFIG_MAX_NR_FILES; i++) {
        if (!fd_table[i].fd_used) {
            fd_table[i].fd_used = true;

            return i;
        }
    }

    return -1;
}

static struct file *__get_opened_file(const char *path)
{
    int i;
    struct file *f;

    for (i = 0; i < CONFIG_MAX_NR_FILES; i++) {
        f = fd_table[i].fd_file;
        if ((f != NULL) && (strcmp(f->fl_path, path) == 0)) {
            return f;
        }
    }

    return NULL;
}

/* check if there is any file opened under a path (dir) */
static bool __has_opened_file(const char *path)
{
    int i;
    size_t len = strlen(path);

    for (i = 0; i < CONFIG_MAX_NR_FILES; i++) {
        if ((fd_table[i].fd_file != NULL) &&
            (strncmp(fd_table[i].fd_file->fl_path, path, len) == 0)) {
            return true;
        }
    }

    return false;
}

/**
 * @brief get the new path that is formatted
 *
 * eg:
 * 1) "//fs/"   =>  "/fs"
 * 2) "/fs//"   =>  "/fs"
 */
static inline char *__get_new_path(const char *path)
{
    char *p = (char *)malloc(strlen(path) + 1);
    char *t = p;
    char ch;
    char last = '\0';

    if (p == NULL) {
        return NULL;
    }

    while ((ch = *path++) != '\0') {
        /* filter out duplicate '/' int path */
        if ((ch == '/') && (last == '/')) {
            continue;
        }

        last = ch;
        *t++ = ch;
    }

    /* remove the last '/' in path */

    if (last == '/') {
        t[-1] = '\0';
    } else {
        t[0] = '\0';
    }

    return p;
}

/**
 * vfs_open - virtual filesystem switch open method
 * @path:  the file path
 * @flags: the open flags
 *
 * return : a positive integer as a fd on success, -1 on error
 */
int vfs_open(const char *path, int flags, ...)
{
    int fd = -1;
    const char *rela_path;
    char *new_path;
    struct file *fp;
    struct mount_point *mp;
    int mode = 0644;
    bool fs_locked = false;

    /* can not open dir */

    if ((path == NULL) || (path[strlen(path) - 1] == '/')) {
        errno = EINVAL;
        return -1;
    }
    if (flags & O_CREAT) {
        va_list valist;
        va_start(valist, flags);
        mode = va_arg(valist, int);   // get mode
        va_end(valist);
    }

    if ((new_path = __get_new_path(path)) == NULL) {
        return -1;
    }

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_PATH;
    }
    fs_locked = true;

    if ((fd = __get_fildes(new_path)) == -1) {
        errno = ENFILE;
        goto EXIT_UNLOCK_FS;
    }

    if ((flags & O_EXCL) && (__get_opened_file(new_path) != NULL)) {
        errno = EACCES;
        goto EXIT_RELEASE_FD;
    }

    if ((mp = __path_resolve(new_path, &rela_path)) == NULL) {
        errno = ENOENT;
        goto EXIT_RELEASE_FD;
    }

    if (*rela_path == '\0') {
        errno = EINVAL;
        goto EXIT_RELEASE_FD;
    }

    /* lock mount point to prevent file deleted */
    if (k_mutex_lock(&mp->mp_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_RELEASE_FD;
    }

    (void)k_mutex_unlock(&fs_lock);
    fs_locked = false;

    fp = (struct file *)malloc(sizeof(struct file));
    if (fp == NULL) {
        goto EXIT_UNLOCK_MP;
    }

    fp->fl_path = new_path;
    fp->fl_flags = flags;
    fp->fl_fops = mp->mp_fs->fs_fops;
    fp->fl_mp = mp;
    fp->fl_refs = 1;
    fp->fl_data = 0;

    fd_table[fd].fd_flags = flags;

    if (fp->fl_fops->open == NULL) {
        errno = ENOTSUP;
        goto EXIT_FREE_FILE;
    }
    if (fp->fl_fops->open(fp, rela_path, flags, mode) != 0) {
        goto EXIT_FREE_FILE;
    }

    fd_table[fd].fd_file = fp;

    mp->mp_refs++;

    (void)k_mutex_unlock(&mp->mp_lock);

    return fd;

EXIT_FREE_FILE:
    free(fp);
EXIT_UNLOCK_MP:
    (void)k_mutex_unlock(&mp->mp_lock);
EXIT_RELEASE_FD:
    fd_table[fd].fd_used = false;
    fd = -1;
EXIT_UNLOCK_FS:
    if (fs_locked) {
        (void)k_mutex_unlock(&fs_lock);
    }
EXIT_FREE_PATH:
    free(new_path);

    return fd;
}

static struct file *__get_file(int fd)
{
    if (fd < 0 || fd >= CONFIG_MAX_NR_FILES) {
        return NULL;
    }
    return fd_table[fd].fd_file;
}

/**
 * vfs_close - virtual filesystem switch close interface
 * @fd: the file descriptor to be closed
 *
 * return : 0 on success, -1 on error
 */
int vfs_close(int fd)
{
    struct file *fp;
    struct mount_point *mp;
    int ret = -1;

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }
    if ((fp = __get_file(fd)) == NULL) {
        errno = EBADF;
        goto EXIT_UNLOCK_FS;
    }
    if (--fp->fl_refs != 0) {
        ret = 0;
        goto EXIT_UNLOCK_FS;
    }

    mp = fp->fl_mp;
    if ((ret = k_mutex_lock(&mp->mp_lock)) != 0) {
        errno = EAGAIN;
        goto EXIT_UNLOCK_FS;
    }
    k_mutex_unlock(&fs_lock);

    ret = 0;
    if (fp->fl_fops->close != NULL) {
        ret = fp->fl_fops->close(fp);
    }

    if (ret == 0) {
        memset(&fd_table[fd], 0, sizeof(struct fildes));
        mp->mp_refs--;
        free(fp->fl_path);
        free(fp);
    } else {
        fp->fl_refs++; /* restore */
    }

    /* ret already 0 */
    k_mutex_unlock(&mp->mp_lock);
    return ret;

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
    return ret;
}

/**
 * vfs_close - virtual filesystem switch read interface
 * @fd:    the file descriptor
 * @buf:   the data buffer
 * @nbyte: the number of bytes
 *
 * return : number of read on success, -1 on error
 */
int vfs_read(int fd, char *buf, size_t nbyte)
{
    struct file *fp;
    struct mount_point *mp;
    int ret = -1;

    if (buf == NULL || nbyte == 0) {
        errno = EINVAL;
        return ret;
    }

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }
    if ((fp = __get_file(fd)) == NULL) {
        errno = EBADF;
        goto EXIT_UNLOCK_FS;
    }
    if (fp->fl_fops->read == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    mp = fp->fl_mp;
    if (k_mutex_lock(&mp->mp_lock) == 0) {
        (void)k_mutex_unlock(&fs_lock);
        ret = fp->fl_fops->read(fp, buf, nbyte);
        (void)k_mutex_unlock(&mp->mp_lock);
        return ret;
    } else {
        errno = EAGAIN;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
    return ret;
}

/**
 * vfs_write - virtual filesystem switch write interface
 * @fd:    the file descriptor
 * @buf:   the data buffer
 * @nbyte: the number of bytes
 *
 * return : number of write on success, -1 on error
 */
int vfs_write(int fd, const char *buf, size_t nbyte)
{
    struct file *fp;
    struct mount_point *mp;
    int ret = -1;

    if (buf == NULL || nbyte == 0) {
        errno = EINVAL;
        return ret;
    }

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }
    if ((fp = __get_file(fd)) == NULL) {
        errno = EBADF;
        goto EXIT_UNLOCK_FS;
    }
    if (fp->fl_fops->write == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    mp = fp->fl_mp;
    if (k_mutex_lock(&mp->mp_lock) == 0) {
        (void)k_mutex_unlock(&fs_lock);
        ret = fp->fl_fops->write(fp, buf, nbyte);
        (void)k_mutex_unlock(&mp->mp_lock);
        return ret;
    } else {
        errno = EAGAIN;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
    return ret;
}

/**
 * vfs_lseek - move the read/write file offset
 * @fd:     the file descriptor
 * @offset: offset bytes
 * @whence: set, cur or end
 *
 * The lseek() function shall set the file offset for the open file description associated with the
 * file descriptor fildes, as follows:
 *      If whence is SEEK_SET, the file offset shall be set to offset bytes.
 *      If whence is SEEK_CUR, the file offset shall be set to its current location plus offset.
 *      If whence is SEEK_END, the file offset shall be set to the size of the file plus offset.
 *
 * return : new offset on success, -1 on error
 */
int vfs_lseek(int fd, int offset, int whence)
{
    struct file *fp;
    struct mount_point *mp;
    int ret = -1;

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }
    if ((fp = __get_file(fd)) == NULL) {
        errno = EBADF;
        goto EXIT_UNLOCK_FS;
    }
    if (fp->fl_fops->lseek == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    mp = fp->fl_mp;
    if (k_mutex_lock(&mp->mp_lock) == 0) {
        (void)k_mutex_unlock(&fs_lock);
        ret = fp->fl_fops->lseek(fp, offset, whence);
        (void)k_mutex_unlock(&mp->mp_lock);
        return ret;
    } else {
        errno = EAGAIN;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
    return ret;
}

/**
 * vfs_ioctl - virtual filesystem switch ioctl interface
 * @fd:  the file descriptor
 * @req: request
 *
 * return : 0 on success, -1 on error
 */
int vfs_ioctl(int fd, int req, ...)
{
    struct file *fp;
    struct mount_point *mp;
    int ret = -1;
    va_list valist;

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }
    if ((fp = __get_file(fd)) == NULL) {
        errno = EBADF;
        goto EXIT_UNLOCK_FS;
    }
    if (fp->fl_fops->lseek == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    mp = fp->fl_mp;
    if (k_mutex_lock(&mp->mp_lock) == 0) {
        (void)k_mutex_unlock(&fs_lock);

        va_start(valist, req);
        ret = fp->fl_fops->ioctl(fp, req, valist);
        va_end(valist);

        (void)k_mutex_unlock(&mp->mp_lock);
        return ret;
    } else {
        errno = EAGAIN;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
    return ret;
}

/**
 * vfs_sync - virtual filesystem switch sync interface
 * @fd: the file descriptor
 *
 * return : 0 on success, -1 on error
 */
int vfs_sync(int fd)
{
    struct file *fp;
    struct mount_point *mp;
    int ret = -1;

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }
    if ((fp = __get_file(fd)) == NULL) {
        errno = EBADF;
        goto EXIT_UNLOCK_FS;
    }
    if (fp->fl_fops->sync == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    mp = fp->fl_mp;
    if (k_mutex_lock(&mp->mp_lock) == 0) {
        (void)k_mutex_unlock(&fs_lock);
        ret = fp->fl_fops->sync(fp);
        (void)k_mutex_unlock(&mp->mp_lock);
        return ret;
    } else {
        errno = EAGAIN;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
    return ret;
}

/**
 * vfs_stat - virtual filesystem switch stat interface
 * @path: the file path
 * @st:   the output stat
 *
 * return : 0 on success, -1 on error
 */
int vfs_stat(const char *path, struct stat *st)
{
    int ret = -1;
    struct mount_point *mp;
    char *new_path;
    const char *rela_path;

    if (path == NULL || st == NULL) {
        errno = EINVAL;
        return ret;
    }
    if ((new_path = __get_new_path(path)) == NULL) {
        errno = EINVAL;
        return ret;
    }
    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_PATH;
    }
    if ((mp = __path_resolve(new_path, &rela_path)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }
    if (mp->mp_fs->fs_mops->stat == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    if (k_mutex_lock(&mp->mp_lock) == 0) {
        k_mutex_unlock(&fs_lock);
        ret = mp->mp_fs->fs_mops->stat(mp, rela_path, st);
        k_mutex_unlock(&mp->mp_lock);
        goto EXIT_FREE_PATH;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
EXIT_FREE_PATH:
    free(new_path);
    return ret;
}

/**
 * vfs_unlink - virtual filesystem switch unlink interface
 * @path: the file path
 *
 * return : 0 on success, -1 on error
 */
int vfs_unlink(const char *path)
{
    int ret = -1;
    struct mount_point *mp;
    char *new_path;
    const char *rela_path;

    if (path == NULL) {
        errno = EINVAL;
        return ret;
    }
    if ((new_path = __get_new_path(path)) == NULL) {
        errno = EINVAL;
        return ret;
    }
    if (k_mutex_lock(&fs_lock) != 0) {
        goto EXIT_FREE_PATH;
    }
    /* file is opened */
    if (__get_opened_file(new_path) != NULL) {
        errno = ENFILE;
        goto EXIT_UNLOCK_FS;
    }

    /* if there is any file under the dir of path */
    if (__has_opened_file(new_path)) {
        errno = EBUSY;
        goto EXIT_UNLOCK_FS;
    }
    if ((mp = __path_resolve(new_path, &rela_path)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }

    if (mp->mp_fs->fs_mops->unlink == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    if (k_mutex_lock(&mp->mp_lock) == 0) {
        k_mutex_unlock(&fs_lock);
        ret = mp->mp_fs->fs_mops->unlink(mp, rela_path);
        k_mutex_unlock(&mp->mp_lock);

        goto EXIT_FREE_PATH;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
EXIT_FREE_PATH:
    free(new_path);
    return ret;
}

/**
 * vfs_rename - virtual filesystem switch rename interface
 * @oldname: the old file path
 * @newname: the new file path
 *
 * return : 0 on success, -1 on error
 */
int vfs_rename(const char *oldname, const char *newname)
{
    struct mount_point *mp;
    char *old_path;
    char *new_path;
    const char *rela_path_old;
    const char *rela_path_new;
    int ret = -1;

    if (oldname == NULL || newname == NULL) {
        errno = EINVAL;
        return -1;
    }

    if ((old_path = __get_new_path(oldname)) == NULL) {
        return -1;
    }
    if ((new_path = __get_new_path(newname)) == NULL) {
        goto EXIT_FREE_OLD_PATH;
    }

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_NEW_PATH;
    }

    /* old or new is already opened file */

    if ((__get_opened_file(old_path) != NULL) || (__get_opened_file(new_path) != NULL) ||
        (__has_opened_file(old_path))) {
        errno = EBUSY;
        goto EXIT_UNLOCK_FS;
    }

    if ((mp = __path_resolve(old_path, &rela_path_old)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }

    if (mp != __path_resolve(new_path, &rela_path_new)) {
        errno = EXDEV;
        goto EXIT_UNLOCK_FS;
    }

    if (mp->mp_fs->fs_mops->rename == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    if (k_mutex_lock(&mp->mp_lock) == 0) {
        (void)k_mutex_unlock(&fs_lock);
        ret = mp->mp_fs->fs_mops->rename(mp, rela_path_old, rela_path_new);
        (void)k_mutex_unlock(&mp->mp_lock);

        goto EXIT_FREE_NEW_PATH;
    }

EXIT_UNLOCK_FS:
    (void)k_mutex_unlock(&fs_lock);
EXIT_FREE_NEW_PATH:
    free(new_path);
EXIT_FREE_OLD_PATH:
    free(old_path);

    return ret;
}

/**
 * vfs_rename - virtual filesystem switch dir open interface
 * @path: the dir path
 *
 * return : dir object pointer on success, NULL on error
 */
struct dir *vfs_opendir(const char *path)
{
    struct mount_point *mp;
    struct dir *dp = NULL;
    const char *rela_path = NULL;
    char *new_path;
    int ret = -1;

    if (path == NULL) {
        errno = EINVAL;
        return NULL;
    }

    if ((new_path = __get_new_path(path)) == NULL) {
        return NULL;
    }

    if ((dp = (struct dir *)malloc(sizeof(struct dir))) == NULL) {
        goto EXIT_FREE_PATH;
    }

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_DIR;
    }

    if ((mp = __path_resolve(path, &rela_path)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }

    if (mp->mp_fs->fs_mops->opendir == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    if (likely(k_mutex_lock(&mp->mp_lock) == 0)) {
        (void)k_mutex_unlock(&fs_lock);
        dp->dr_mp = mp;   // before opendir
        ret = mp->mp_fs->fs_mops->opendir(mp, dp, rela_path);
        (void)k_mutex_unlock(&mp->mp_lock);
        if (ret != 0) {
            goto EXIT_FREE_DIR;
        }
        free(new_path);
        mp->mp_refs++;
        return dp;
    } else {
        errno = EAGAIN;
    }

EXIT_UNLOCK_FS:
    (void)k_mutex_unlock(&fs_lock);
EXIT_FREE_DIR:
    free(dp);
EXIT_FREE_PATH:
    free(new_path);

    return NULL;
}

/**
 * vfs_readdir - virtual filesystem switch dir read interface
 * @dp: the dir object
 *
 * return : dirent object pointer on success, NULL on error
 */
struct dirent *vfs_readdir(struct dir *dp)
{
    struct mount_point *mp;
    struct dirent *ent = NULL;

    if (dp == NULL) {
        errno = EBADF;
        return NULL;
    }
    if ((mp = dp->dr_mp) == NULL) {
        errno = ENOENT;
        return NULL;
    }

    if (k_mutex_lock(&mp->mp_lock) != 0) {
        errno = EAGAIN;
        return NULL;
    }

    if (mp->mp_fs->fs_mops->readdir == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_MP;
    }
    if (mp->mp_fs->fs_mops->readdir(mp, dp, &dp->dr_dent) == 0) {
        ent = &dp->dr_dent;
    }

EXIT_UNLOCK_MP:
    (void)k_mutex_unlock(&mp->mp_lock);
    return ent;
}

/**
 * vfs_closedir - virtual filesystem switch dir close interface
 * @dp: the dir object
 *
 * return : 0 on on success, -1 on error
 */
int vfs_closedir(struct dir *dp)
{
    struct mount_point *mp;
    int ret = -1;

    if (dp == NULL) {
        errno = EBADF;
        return -1;
    }
    if ((mp = dp->dr_mp) == NULL) {
        errno = ENOENT;
        return -1;
    }

    if (k_mutex_lock(&mp->mp_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }

    if (mp->mp_fs->fs_mops->closedir == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_MP;
    }
    if ((ret = mp->mp_fs->fs_mops->closedir(mp, dp)) == 0) {
        free(dp);
        mp->mp_refs--;
    }

EXIT_UNLOCK_MP:
    (void)k_mutex_unlock(&mp->mp_lock);
    return ret;
}

/**
 * vfs_mkdir - virtual filesystem switch dir create interface
 * @path: the dir path
 * @mode: permission mode
 *
 * return : 0 on on success, -1 on error
 */
int vfs_mkdir(const char *path, int mode)
{
    struct mount_point *mp;
    char *new_path;
    const char *rela_path;
    int ret = -1;

    if (path == NULL) {
        errno = EINVAL;
        return -1;
    }

    if ((new_path = __get_new_path(path)) == NULL) {
        return -1;
    }

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_PATH;
    }

    if ((mp = __path_resolve(new_path, &rela_path)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }
    /* file/dir existed */
    if (*rela_path == '\0') {
        errno = EEXIST;
        goto EXIT_UNLOCK_FS;
    }

    if (unlikely(k_mutex_lock(&mp->mp_lock) != 0)) {
        errno = EAGAIN;
        goto EXIT_UNLOCK_FS;
    }

    (void)k_mutex_unlock(&fs_lock);

    if (unlikely(mp->mp_fs->fs_mops->mkdir != NULL)) {
        ret = mp->mp_fs->fs_mops->mkdir(mp, rela_path, mode);
    } else {
        errno = ENOTSUP;
    }

    (void)k_mutex_unlock(&mp->mp_lock);

    free(new_path);

    return ret;

EXIT_UNLOCK_FS:
    (void)k_mutex_unlock(&fs_lock);
EXIT_FREE_PATH:
    free(new_path);

    return ret;
}

int vfs_rmdir(const char *path)
{
    int ret = -1;
    struct mount_point *mp;
    char *new_path;
    const char *rela_path;

    if (path == NULL) {
        errno = EINVAL;
        return ret;
    }
    if ((new_path = __get_new_path(path)) == NULL) {
        return ret;
    }
    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_PATH;
    }
    /* file is opened */
    if (__get_opened_file(new_path) != NULL) {
        errno = EBUSY;
        goto EXIT_UNLOCK_FS;
    }
    /* if there is any file under the dir of path */
    if (__has_opened_file(new_path)) {
        errno = EBUSY;
        goto EXIT_UNLOCK_FS;
    }
    if ((mp = __path_resolve(new_path, &rela_path)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }
    if (mp->mp_fs->fs_mops->rmdir == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    if (k_mutex_lock(&mp->mp_lock) == 0) {
        k_mutex_unlock(&fs_lock);
        ret = mp->mp_fs->fs_mops->rmdir(mp, rela_path);
        k_mutex_unlock(&mp->mp_lock);

        goto EXIT_FREE_PATH;
    }

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
EXIT_FREE_PATH:
    free(new_path);
    return ret;
}

/**
 * get file_system struct by using name, note: this routine must be invoked with
 * the fs_lock locked
 */
static struct file_system *__get_fs_by_name(const char *name)
{
    dlist_t *iter;
    struct file_system *fs;

    dlist_foreach(iter, &fs_head)
    {
        fs = container_of(iter, struct file_system, fs_node);
        if (strcmp(name, fs->fs_name) == 0) {
            return fs;
        }
    }

    return NULL;
}

/**
 * vfs_mount - virtual filesystem switch mount interface
 * @fs_name: the filesystem name
 * @mp_path: the mount point path name
 *
 * more variable is passed depend on the filesystem type
 *
 * return : 0 on success, negative value on error
 */
int vfs_mount(const char *fs_name, const char *mp_path, ...)
{
    int ret;
    struct file_system *fs;
    struct mount_point *mp;
    va_list valist;
    char *new_path;
    const char *rela_path;

    if (fs_name == NULL || mp_path == NULL || mp_path[0] != '/') {
        errno = EINVAL;
        return -1;
    }

    if ((new_path = __get_new_path(mp_path)) == NULL) {
        return -1;
    }

    /* there is no '/' in the end of mp_name, see __get_new_path */

    if ((ret = k_mutex_lock(&fs_lock)) != 0) {
        errno = EAGAIN;
        goto EXIT_FREE_PATH;
    }

    if ((fs = __get_fs_by_name(fs_name)) == NULL) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }

    /* the mp_path is exist */
    if ((__path_resolve(new_path, &rela_path) != NULL) && (*rela_path == '\0')) {
        errno = EEXIST;
        goto EXIT_UNLOCK_FS;
    }

    if (fs->fs_mops->mount == NULL) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }

    mp = (struct mount_point *)malloc(sizeof(struct mount_point));
    if (mp == NULL) {
        errno = ENOMEM;
        goto EXIT_UNLOCK_FS;
    }

    mp->mp_path = new_path;
    mp->mp_fs = fs;
    mp->mp_refs = 0;

    if (k_mutex_init(&mp->mp_lock) != 0) {
        goto EXIT_FREE_MP;
    }

    va_start(valist, mp_path);
    ret = fs->fs_mops->mount(mp, valist);
    va_end(valist);

    if (ret != 0) {
        goto EXIT_FREE_MP;
    }

    dlist_add(&mp_head, &mp->mp_node);

    fs->fs_refs++;

    k_mutex_unlock(&fs_lock);

    return 0;

EXIT_FREE_MP:
    free(mp);
EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
EXIT_FREE_PATH:
    free(new_path);

    return -1;
}

/**
 * vfs_unmount - virtual filesystem switch unmount interface
 * @path: the mount point path name
 *
 * return : 0 on success, negative value on error
 */
int vfs_unmount(const char *path, ...)
{
    struct mount_point *mp;
    va_list valist;
    char *new_path;
    const char *rela_path;
    int ret;

    if (path == NULL) {
        errno = EINVAL;
        return -1;
    }
    if ((new_path = __get_new_path(path)) == NULL) {
        return -1;
    }
    if (unlikely(k_mutex_lock(&fs_lock) != 0)) {
        errno = EAGAIN;
        goto EXIT_FREE_PATH;
    }

    mp = __path_resolve(new_path, &rela_path);
    if (unlikely((mp == NULL) || (*rela_path != '\0'))) {
        errno = ENOENT;
        goto EXIT_UNLOCK_FS;
    }

    if (unlikely(mp->mp_refs != 0)) {
        errno = EBUSY;
        goto EXIT_UNLOCK_FS;
    }

    if (unlikely(mp->mp_fs->fs_mops->unmount == NULL)) {
        errno = ENOTSUP;
        goto EXIT_UNLOCK_FS;
    }
    va_start(valist, path);
    ret = mp->mp_fs->fs_mops->unmount(mp, valist);
    va_end(valist);
    if (ret != 0) {
        goto EXIT_UNLOCK_FS;
    }

    dlist_del(&mp->mp_node);

    mp->mp_fs->fs_refs--;

    k_mutex_destroy(&mp->mp_lock);
    free(mp->mp_path);
    free(mp);
    free(new_path);

    k_mutex_unlock(&fs_lock);

    return 0;

EXIT_UNLOCK_FS:
    k_mutex_unlock(&fs_lock);
EXIT_FREE_PATH:
    free(new_path);
    return -1;
}

static bool __is_fs_name_valid(const char *fs_name)
{
    int ch;
    const char *name = fs_name;

    while ((ch = *name++) != '\0') {
        if (!islower(ch | 0x20) && !isdigit(ch)) {
            return false;
        }
    }

    return name != fs_name;
}

/**
 * vfs_fs_register - register a filesystem to the virtual filesystem switch
 * @fs: the filesystem struct
 *
 * return : 0 on success, negative value on error
 */
int vfs_fs_register(struct file_system *fs)
{
    int ret = -1;

    BUG_ON(fs == NULL || fs->fs_name == NULL, "Invalid filesystem!");
    BUG_ON(fs->fs_fops == NULL || fs->fs_fops->open == NULL || fs->fs_fops->read == NULL ||
               fs->fs_fops->write == NULL,
           "Invalid file ops!");
    BUG_ON(fs->fs_mops == NULL || fs->fs_mops->stat == NULL || fs->fs_mops->mount == NULL,
           "Invalid mount ops!");

    /* check fs name */

    BUG_ON(!__is_fs_name_valid(fs->fs_name), "Invalid filesystem name!");

    if (k_mutex_lock(&fs_lock) != 0) {
        errno = EAGAIN;
        return -1;
    }

    if (__get_fs_by_name(fs->fs_name) == NULL) {
        dlist_add(&fs_head, &fs->fs_node);
        fs->fs_refs = 0;
        ret = 0;
    } else {
        errno = EEXIST;
    }

    k_mutex_unlock(&fs_lock);

    return ret;
}

/**
 * vfs_fs_unregister - unregister a filesystem from the virtual filesystem switch
 * @fs: the filesystem struct
 *
 * return : 0 on success, negative value on error
 */
int vfs_fs_unregister(struct file_system *fs)
{
    int ret;

    if (fs == NULL) {
        return -1;
    }

    if (unlikely(k_mutex_lock(&fs_lock) != 0)) {
        errno = EAGAIN;
        return -1;
    }

    if (fs->fs_refs != 0) {
        WARN("Filesystem is busy!");
        errno = EBUSY;
        ret = -1;
    } else {
        dlist_del(&fs->fs_node);
        ret = 0;
    }

    k_mutex_unlock(&fs_lock);

    return ret;
}

/**
 * vfs_init - the virtual filesystem switch initialization routine
 *
 * return : 0 on success, negative value on error
 */
static int vfs_init(void)
{
    return k_mutex_init(&fs_lock);
}

MODULE_INIT(postkernel, vfs_init);

#ifdef CONFIG_CMDER
static char *__cwd = NULL;

static void __ls_root(cmder_t *cmder)
{
    dlist_t *iter;
    char *pch;

    dlist_foreach(iter, &mp_head)
    {
        pch = container_of(iter, struct mount_point, mp_node)->mp_path;

        do {
            cmder_printf(cmder, "%c", *pch++);
        } while ((*pch != '/') && (*pch != '\0'));

        cmder_printf(cmder, "\n");
    }
}

static int __cmd_ls(cmder_t *cmder, int argc, char *argv[])
{
    struct dir *dir;
    struct dirent *ent;

    if (argc == 1) {
        if (__cwd == NULL) {
            __ls_root(cmder);
            return 0;
        }

        dir = vfs_opendir(__cwd);
    } else {
        if (argv[1][0] == '/') {
            if (argv[1][1] == '\0') {
                __ls_root(cmder);
                return 0;
            }

            dir = vfs_opendir(argv[1]);
        } else {
            char *path;

            if (__cwd == NULL) {
                cmder_printf(cmder, "error: file not exit!\n");
                return -1;
            }

            /* +2 for '/' and '\0' */

            path = malloc(strlen(__cwd) + strlen(argv[1]) + 2);

            if (path == NULL) {
                cmder_printf(cmder, "error: fail to build up path!\n");
                return -1;
            }

            sprintf(path, "%s/%s", __cwd, argv[1]);

            dir = vfs_opendir(path);

            free(path);
        }
    }

    if (dir == NULL) {
        cmder_printf(cmder, "error: fail to open dir\n");
        return -1;
    }

    while ((ent = vfs_readdir(dir)) != NULL) {
        cmder_printf(cmder, "%s\n", ent->d_name);
    };

    vfs_closedir(dir);

    return 0;
}

CMDER_CMD_DEF("ls", "list a directory", __cmd_ls);

static void __show_mount(cmder_t *cmder)
{
    dlist_t *iter;

    dlist_foreach(iter, &mp_head)
    {
        struct mount_point *mp;

        mp = container_of(iter, struct mount_point, mp_node);

        cmder_printf(cmder, "%s @ %s\n", mp->mp_fs->fs_name, mp->mp_path);
    }
}

static int __cmd_mount(cmder_t *cmder, int argc, char *argv[])
{
    if (argc == 1) {
        __show_mount(cmder);
        return 0;
    }

    return -1;
}

// TODO: show information about mounted filesystems, or mount a filesystem

CMDER_CMD_DEF("mount", "show information about mounted filesystems", __cmd_mount);

struct cmd_cat_info {
    cmder_t *cmder;
    int fd;
};

static int __cat_task(uintptr_t arg)
{
    struct cmd_cat_info *info = (struct cmd_cat_info *)arg;
    char c;

    while (vfs_read(info->fd, &c, 1) != 0) {
        cmder_printf(info->cmder, "%02x", c);
    }

    return 0;
}

static int __cmd_cat(cmder_t *cmder, int argc, char *argv[])
{
    struct cmd_cat_info info;
    task_id cat_task;

    if (argc == 1) {
        cmder_printf(cmder, "usage: ls <abs_path>\n");
        return -1;
    }

    info.cmder = cmder;

    if (argv[1][0] == '/') {
        info.fd = vfs_open(argv[1], O_RDONLY);
    } else {
        char *path;

        if (__cwd == NULL) {
            cmder_printf(cmder, "error: file not exit!\n");
            return -1;
        }

        /* +2 for '/' and '\0' */

        path = malloc(strlen(__cwd) + strlen(argv[1]) + 2);

        if (path == NULL) {
            cmder_printf(cmder, "error: fail to build up path!\n");
            return -1;
        }

        sprintf(path, "%s/%s", __cwd, argv[1]);

        info.fd = vfs_open(path, O_RDONLY);

        free(path);
    }

    if (info.fd == -1) {
        cmder_printf(cmder, "error: fail to open file\n");
        return -1;
    }

    cat_task = task_spawn("cat", 30, 0, 0x500, __cat_task, (uintptr_t)&info);

    if (unlikely(cat_task == NULL)) {
        cmder_printf(cmder, "error: fail to create cat task!\n");
        vfs_close(info.fd);
        return -1;
    }

    while (1) {
        unsigned char input = cmder->getc(cmder->arg);

        switch (input) {
        case 'q':
        case cmder_vk_ctrlc:
        case cmder_vk_ctrld:
            task_delete(cat_task);
            vfs_close(info.fd);
            return 0;
        default:
            break;
        }
    }
}

CMDER_CMD_DEF("cat", "concatenate a file to shell output", __cmd_cat);

static int __cd(cmder_t *cmder, char *path)
{
    struct dir *dir;
    char *cwd;

    if (strcmp(path, "/") == 0) {
        if (__cwd != NULL) {
            free(__cwd);
            __cwd = NULL;

            return 0;
        }
    }

    /* use opendir to test if dir valid */

    if ((dir = vfs_opendir(path)) == NULL) {
        cmder_printf(cmder, "error: fail to open dir!\n");
        return -1;
    }

    vfs_closedir(dir);

    if ((__cwd != NULL) && (strlen(__cwd) >= strlen(path))) {
        strcpy(__cwd, path);
        return 0;
    }

    cwd = __get_new_path(path);

    if (cwd == NULL) {
        cmder_printf(cmder, "error: fail to build up path!\n");
        return -1;
    }

    if (__cwd != NULL) {
        free(__cwd);
    }

    __cwd = cwd;

    return 0;
}

static int __cmd_cd(cmder_t *cmder, int argc, char *argv[])
{
    char *path;
    int ret;
    char *cwd;

    if (argc == 1) {
        return __cd(cmder, "/");
    }

    if (argv[1][0] == '/') {
        return __cd(cmder, argv[1]);
    }

    cwd = __cwd == NULL ? "/" : __cwd;

    /* +2 for '/' and '\0' */

    path = malloc(strlen(__cwd) + strlen(argv[1]) + 2);

    if (path == NULL) {
        cmder_printf(cmder, "error: fail to build up path!\n");
        return -1;
    }

    sprintf(path, "%s/%s", cwd, argv[1]);

    ret = __cd(cmder, path);

    free(path);

    return ret;
}

CMDER_CMD_DEF("cd", "change the shell working directory", __cmd_cd);

static int __cmd_pwd(cmder_t *cmder, int argc, char *argv[])
{
    cmder_printf(cmder, "%s\n", __cwd == NULL ? "/" : __cwd);

    return 0;
}

CMDER_CMD_DEF("pwd", "print the current working directory", __cmd_pwd);
#endif
